package cn.itcast.hotel.service.impl;

import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.pojo.Result;
import cn.itcast.hotel.pojo.SearchDto;
import cn.itcast.hotel.service.HotelSearchService;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;

import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.InternalOrder;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortBuilders;

import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class HotelSearchServiceImpl implements HotelSearchService {
    @Autowired
    private RestHighLevelClient client;

    @Override
    public Result<HotelDoc> searchAll(SearchDto searchDto) throws IOException {
        SearchRequest request = new SearchRequest("hotel");
        BoolQueryBuilder queryBuilder = getBoolQueryBuilder(searchDto);


        //设置查询条件
        request.source().query(queryBuilder);
        //分页
        Integer page = searchDto.getPage();
        Integer size = searchDto.getSize();
        request.source().from((page - 1) * size).size(size);
        //根据地理坐标查询
        String location = searchDto.getLocation();
        if (StringUtils.isNotEmpty(location)){
            String[] s = location.split(",");
            double lat = Double.parseDouble(s[0]);
            double lon = Double.parseDouble(s[1]);
            request.source().sort(SortBuilders.geoDistanceSort("location",lat,lon)
                    .order(SortOrder.ASC)
                    .unit(DistanceUnit.KILOMETERS));
        }
        // 2.算分控制
        FunctionScoreQueryBuilder functionScoreQuery =
                QueryBuilders.functionScoreQuery(
                        // 原始查询，相关性算分的查询
                        queryBuilder,
                        // function score的数组
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                                // 其中的一个function score 元素
                                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                        // 过滤条件
                                        QueryBuilders.termQuery("isAD", true),
                                        // 算分函数
                                        ScoreFunctionBuilders.weightFactorFunction(10)
                                )
                        });

        request.source().query(functionScoreQuery);
        //发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        //解析响应参数
        SearchHits hits = response.getHits();
        //获取总条数
        long value = hits.getTotalHits().value;

        //文档数组
        SearchHit[] hitsHits = hits.getHits();
        //遍历,并封装集合返回
        List<HotelDoc> hotelDocs = new ArrayList<>();
        for (SearchHit hit : hitsHits) {
            String json = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);

            Object[] distanceValue = hit.getSortValues();
            if (distanceValue != null && distanceValue.length > 0) {
                double distance = Double.parseDouble(distanceValue[0].toString());
                hotelDoc.setDistance(distance);
            }
            hotelDocs.add(hotelDoc);
        }

        return new Result<>(value,hotelDocs);
    }

    private BoolQueryBuilder getBoolQueryBuilder(SearchDto searchDto) {

        BoolQueryBuilder queryBuilder = new BoolQueryBuilder();
        //获取查询参数
        String key = searchDto.getKey();
        if (StringUtils.isEmpty(key)) {
            queryBuilder = queryBuilder.must(QueryBuilders.matchAllQuery());
        } else {
            queryBuilder = queryBuilder.must(QueryBuilders.matchQuery("all", key));
        }
        //设置过滤条件
        String brand  = searchDto.getBrand();
        if (StringUtils.isNotEmpty(brand)){
            queryBuilder.filter(QueryBuilders.termQuery("brand",brand));
        }
        String city = searchDto.getCity();
        if (StringUtils.isNotEmpty(city)) {
            queryBuilder.filter(QueryBuilders.termQuery("city",city));
        }
        String starName = searchDto.getStarName();
        if (StringUtils.isNotEmpty(starName)) {
            queryBuilder.filter(QueryBuilders.termQuery("starName",starName));
        }
        Integer maxPrice = searchDto.getMaxPrice();
        Integer minPrice = searchDto.getMinPrice();
        if (maxPrice != null){
            queryBuilder.filter(QueryBuilders.rangeQuery("price").lte(maxPrice));
        }
        if (minPrice != null){
            queryBuilder.filter(QueryBuilders.rangeQuery("price").gte(minPrice));
        }

        return queryBuilder;
    }

    @Override
    public Map<String, List<String>> filters(SearchDto searchDto) throws IOException {
        //创建查询对象
        SearchRequest request = new SearchRequest("hotel");
        //设置查询条件
        BoolQueryBuilder queryBuilder = getBoolQueryBuilder(searchDto);
        request.source().query(queryBuilder);
        //Bucket聚合
        request.source().aggregation(AggregationBuilders.terms("brandAggs").field("brand").size(5));
        request.source().aggregation(AggregationBuilders.terms("cityAggs").field("city").size(5));
        request.source().aggregation(AggregationBuilders.terms("starNamedAggs").field("starName").size(5));
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        Aggregations aggregations = response.getAggregations();
        List<String> cityList = praseResult(aggregations, "cityAggs");
        List<String> brandList = praseResult(aggregations, "brandAggs");
        List<String> starNameList = praseResult(aggregations, "starNamedAggs");

        Map<String, List<String>> map = new HashMap<>();
        map.put("city",cityList);
        map.put("brand",brandList);
        map.put("starName",starNameList);
        return map;
    }

    @Override
    public List<String> getSuggestions(String key) throws IOException {
        //准备请求对象
        SearchRequest request = new SearchRequest("hotel");
        //设置请求参数
        request.source().suggest(new SuggestBuilder().addSuggestion("suggestions", SuggestBuilders
                .completionSuggestion("suggestion")
                .prefix(key).skipDuplicates(true).size(10)));
        //发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //解析结果
        Suggest suggest = response.getSuggest();
        //根据名称获取补全结果
        Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestions = suggest.getSuggestion("suggestions");
        //获取option遍历,并添加到集合中
        List<String> list = new ArrayList<>();
        for (Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option> suggestion : suggestions) {
            for (Suggest.Suggestion.Entry.Option option : suggestion.getOptions()) {
                String s = option.getText().toString();
                list.add(s);
            }
        }
        return list;
    }

    /**
     * 解析桶聚合结果
     */
    public List<String> praseResult(Aggregations aggregations,String name) {
        Terms terms = aggregations.get(name);
        List<? extends Terms.Bucket> buckets = terms.getBuckets();
        List<String> list = new ArrayList<>();
        for (Terms.Bucket bucket : buckets) {
            String key = bucket.getKeyAsString();
            list.add(key);
        }
        return list;
    }
}
